1 module hip.font.bmfont; 2 import hip.api.data.font; 3 import hip.api.renderer.texture; 4 import hip.error.handler; 5 6 7 class HipBitmapFont : HipFont 8 { 9 ///The atlas path is saved inside the class 10 string atlasPath; 11 ///This variable is defined when the atlas is being read 12 string atlasTexturePath; 13 14 ///Use that property to know how many characters was read inside the atlas 15 uint charactersCount; 16 17 uint height; 18 19 private string error; 20 21 22 HipFontKerning kerning; 23 24 bool loadAtlas(string data, string atlasPath) 25 { 26 import hip.util.string; 27 this.atlasPath = atlasPath; 28 29 static int advanceSpace(string data, int i) 30 { 31 while(i < data.length && (data[i].isWhitespace || data[i] == '=')) 32 i++; 33 return i; 34 } 35 static int nextToken(string data, int i) 36 { 37 i = advanceSpace(data, i); 38 if(i >= data.length) 39 return -1; 40 else if(data[i] == '"') //Find the '"' 41 { 42 i++; 43 while(i < data.length && data[i] != '"') 44 { 45 if(data[i] == '\\') 46 i++; 47 i++; 48 } 49 if(i < data.length)i++; 50 51 } 52 else if(data[i].isAlpha) 53 { 54 while(i <= data.length && data[i].isAlpha) 55 i++; 56 } 57 else if(data[i].isNumeric) 58 { 59 while(i <= data.length && data[i].isNumeric) 60 i++; 61 } 62 return i; 63 } 64 65 static int getNextInt(string data, ref int i) 66 { 67 import hip.util.conv; 68 int start = advanceSpace(data, i); 69 i = nextToken(data, start); 70 if(i != -1) 71 { 72 return to!int(data[start..i]); 73 } 74 return i; 75 } 76 77 static string getNextString(string data, ref int i) 78 { 79 int start = advanceSpace(data, i); 80 i = nextToken(data, start); 81 if(i != -1) 82 { 83 if(data[start] == '"') 84 return data[start+1..i-1]; 85 else 86 return data[start..i]; 87 } 88 return ""; 89 } 90 string name; 91 int size; 92 93 int bold, italic; 94 string charset; 95 int unicode; 96 int stretchH; 97 int smooth, aa; 98 99 int paddingX, paddingY, paddingW, paddingH; 100 int spacingX, spacingY; 101 int outline; 102 103 //Common 104 int lineHeight, base, scaleW, scaleH, pages, packed, alpha, red, green,blue; 105 //Page 106 int pageId; 107 //chars 108 int count; 109 110 int index = 0; 111 enum Context 112 { 113 info, 114 common, 115 page, 116 chars, 117 kernings, 118 unknown 119 } 120 int context = Context.unknown; 121 string key; 122 while(index != -1 && index < data.length) 123 { 124 key = getNextString(data, index); 125 final switch(context) 126 { 127 case Context.info: 128 { 129 switch(key) 130 { 131 case "face": 132 name = getNextString(data, index); 133 break; 134 case "size": 135 size = getNextInt(data, index); 136 height = size; 137 break; 138 case "bold": 139 bold = getNextInt(data, index); 140 break; 141 case "italic": 142 italic = getNextInt(data, index); 143 break; 144 case "charset": 145 charset = getNextString(data, index); 146 break; 147 case "unicode": 148 unicode = getNextInt(data, index); 149 break; 150 case "stretchH": 151 stretchH = getNextInt(data, index); 152 break; 153 case "smooth": 154 smooth = getNextInt(data, index); 155 break; 156 case "aa": 157 aa = getNextInt(data, index); 158 break; 159 case "padding": 160 paddingX = getNextInt(data, index); 161 index++; 162 paddingY = getNextInt(data, index); 163 index++; 164 paddingW = getNextInt(data, index); 165 index++; 166 paddingH = getNextInt(data, index); 167 index++; 168 break; 169 case "spacing": 170 spacingX = getNextInt(data, index); 171 index++; 172 spacingY = getNextInt(data, index); 173 index++; 174 break; 175 case "outline": 176 outline = getNextInt(data, index); 177 break; 178 default: 179 goto checkUnknown; 180 } 181 break; 182 } 183 case Context.common: 184 { 185 int tempIndex = index; 186 int next = getNextInt(data, index); 187 switch(key) 188 { 189 case "lineHeight": 190 lineHeight = next; 191 break; 192 case "base": 193 base = next; 194 break; 195 case "scaleW": 196 scaleW = next; 197 break; 198 case "scaleH": 199 scaleH = next; 200 break; 201 case "pages": 202 pages = next; 203 break; 204 case "packed": 205 packed = next; 206 break; 207 case "alphaChnl": 208 alpha = next; 209 break; 210 case "redChnl": 211 red = next; 212 break; 213 case "greenChnl": 214 green = next; 215 break; 216 case"blueChnl": 217 blue = next; 218 break; 219 default: 220 index = tempIndex; 221 goto checkUnknown; 222 } 223 break; 224 } 225 case Context.page: 226 { 227 switch(key) 228 { 229 case "id": 230 pageId = getNextInt(data, index); 231 break; 232 case "file": 233 atlasTexturePath = getNextString(data, index); 234 break; 235 default: 236 goto checkUnknown; 237 } 238 break; 239 } 240 case Context.chars: 241 { 242 //Advance "count" 243 charactersCount = count = getNextInt(data, index); 244 uint maxWidth = 0; 245 246 247 for(int i = 0; i < count; i++) 248 { 249 HipFontChar ch; 250 int*[10] fields = [cast(int*)&ch.id, &ch.x, &ch.y, &ch.width, &ch.height, &ch.xoffset, &ch.yoffset, &ch.xadvance, &ch.page, &ch.chnl]; 251 //Advance "char" 252 index = nextToken(data, index); 253 for(int fIndex = 0; fIndex < fields.length; fIndex++) 254 { 255 index = nextToken(data, index); 256 *fields[fIndex] = getNextInt(data, index); 257 } 258 if(ch.width > maxWidth) 259 maxWidth = ch.width; 260 characters[ch.id] = ch; 261 } 262 auto space = ' ' in characters; 263 if(space is null || (space.width == 0 && space.xadvance == 0)) 264 spaceWidth = maxWidth; 265 else 266 spaceWidth = space.xadvance > space.width ? space.xadvance : space.width; 267 lineBreakHeight = lineHeight; 268 context = Context.unknown; 269 break; 270 } 271 case Context.kernings: 272 { 273 //Advance "count" 274 index = nextToken(data, index); 275 int kerningCount = getNextInt(data, index); 276 for(int i = 0; i < kerningCount; i++) 277 { 278 //Advance "kerning " 279 index = nextToken(data, index); 280 int[3] values = void; //first, second, amount 281 for(int fIndex = 0; fIndex < 3; fIndex++) 282 { 283 index = nextToken(data, index); 284 values[fIndex] = getNextInt(data, index); 285 } 286 if((values[0] in kerning) is null) 287 kerning[values[0]] = HipCharKerning.init; 288 kerning[values[0]][values[1]] = values[2]; 289 } 290 break; 291 } 292 //Tries to find the context 293 checkUnknown: case Context.unknown: 294 contextSwitch: switch(key) 295 { 296 static foreach(mem; __traits(allMembers, Context)) 297 { 298 case mem: 299 context = __traits(getMember, Context, mem); 300 break contextSwitch; 301 } 302 default: 303 assert(false, "Unknown key received: "~key); 304 } 305 continue; 306 } 307 } 308 309 310 return true; 311 } 312 313 bool loadTexture(IHipTexture t) 314 { 315 texture = t; 316 int width = t.getWidth; 317 int height = t.getHeight; 318 if(width == 0 || height == 0) 319 return false; 320 321 foreach(ref ch; characters) 322 { 323 if(ch.id != 0) 324 { 325 ch.normalizedX = cast(float)ch.x/width; 326 ch.normalizedY = cast(float)ch.y/height; 327 ch.normalizedWidth = cast(float)ch.width/width; 328 ch.normalizedHeight = cast(float)ch.height/height; 329 } 330 } 331 return true; 332 } 333 334 override uint getHeight() const { return height; } 335 336 string getTexturePath() 337 { 338 import hip.util.path; 339 string texturePath; 340 if(atlasTexturePath != "") 341 { 342 string atlasDir = atlasPath.dirName; 343 if(atlasDir != atlasPath) 344 texturePath = atlasDir.joinPath(atlasTexturePath); 345 else 346 texturePath = atlasTexturePath; 347 } 348 return texturePath; 349 } 350 351 /** 352 * This won't do anything in case of a bitmap font, as no one can change it. 353 */ 354 override HipFont getFontWithSize(uint size) 355 { 356 HipBitmapFont ret = new HipBitmapFont(); 357 ret.atlasPath = this.atlasPath; 358 ret.atlasTexturePath = this.atlasTexturePath; 359 ret.kerning = cast(HipFontKerning)this.kerning; 360 ret.charactersCount = this.charactersCount; 361 ret._texture = cast(IHipTexture)this._texture; 362 return cast(HipFont)ret; 363 } 364 void readTexture(string texturePath = "") 365 { 366 texturePath = texturePath == "" ? getTexturePath() : texturePath; 367 // auto t = new HipTexture(); 368 // t.load(texturePath); 369 // loadTexture(t); 370 } 371 372 373 static HipBitmapFont fromFile(string atlasPath, string texturePath = "") 374 { 375 auto ret = new HipBitmapFont(); 376 ret.loadAtlas("", atlasPath); 377 ret.readTexture(texturePath); 378 return ret; 379 } 380 381 override int getKerning(const(HipFontChar)* current, const(HipFontChar)* next) const 382 { 383 return getKerning(dchar(current.id), dchar(next.id)); 384 } 385 override int getKerning(dchar current, dchar next) const 386 { 387 const HipCharKerning* chKerning = current in kerning; 388 if(chKerning is null) 389 return 0; 390 const int* kerningValue = next in (*chKerning); 391 if(kerningValue is null) 392 return 0; 393 return *kerningValue; 394 } 395 396 397 }